/*
 * Copyright 2002-2017 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.beans.factory.config;

import org.springframework.lang.Nullable;
import org.springframework.util.StringValueResolver;

/**
 * EmbeddedValueResolver 是 Spring 框架中的一个工具，用于解析字符串中的占位符（如 ${...} 或 #{...}），
 * 并将它们替换为相应的属性值或 SpEL（Spring Expression Language）表达式的计算结果。
 * 这个功能特别有用，尤其是在需要处理包含占位符的配置字符串时，比如在视图模板、消息格式化或者任何其他需要动态插入值的地方。
 *
 * 1. 主要用途
 * 		解析占位符：将形如 ${propertyKey} 或 #{expression} 的占位符替换为实际的值。
 * 		支持默认值：可以指定默认值，当占位符对应的属性不存在时使用，默认值以冒号分隔，例如 ${propertyKey:defaultValue}。
 * 		SpEL 表达式支持：不仅限于简单的属性查找，还可以解析复杂的 SpEL 表达式，从而实现更灵活的值替换逻辑。
 *
 * 2. 工作原理
 * 		EmbeddedValueResolver 通过调用 BeanFactory 或 ApplicationContext 提供的方法来解析占位符。
 * 		它利用了 Spring 的 PropertyPlaceholderConfigurer 和 Environment 接口，这些接口负责加载和管理应用程序的属性源（如属性文件、环境变量等）。
 * 		对于 SpEL 表达式，则依赖于 ExpressionParser 来进行求值。
 *
 * 3. 使用场景
 * 		国际化消息：在消息资源中使用占位符，根据用户的语言环境动态生成适合的消息文本。
 * 		视图模板渲染：在 Thymeleaf、FreeMarker 等模板引擎中使用占位符，以便在渲染页面时插入动态数据。
 * 		日志记录：构建日志信息时，可以嵌入占位符，使得日志输出更加灵活和可读。
 * 		配置文件解析：在应用启动时解析含有占位符的配置项，确保所有必要的配置都能正确初始化。
 *
 * 4. 示例代码
 * 		下面是一个简单的例子，展示了如何在自定义类中使用 EmbeddedValueResolver：
 *		import org.springframework.context.EmbeddedValueResolverAware;
 * 		import org.springframework.context.ApplicationContext;
 * 		import org.springframework.stereotype.Component;
 *
 * 		@Component
 * 		public class CustomComponent implements EmbeddedValueResolverAware {
 *     		private String resolvedMessage;
 *     		@Override
 *     		public void setEmbeddedValueResolver(ApplicationContext context) {
 *         		// 使用 ApplicationContext 解析占位符
 *         		String messageTemplate = "Hello, ${user.name}!";
 *         		this.resolvedMessage = context.resolveEmbeddedValue(messageTemplate);
 *     		}
 *     		public String getResolvedMessage() {
 *         		return resolvedMessage;
 *     		}
 * 		}
 *		在这个例子中，CustomComponent 实现了 EmbeddedValueResolverAware 接口，并覆写了 setEmbeddedValueResolver 方法。
 *		这样，Spring 容器会在创建该组件实例时自动注入 ApplicationContext，并允许我们通过 resolveEmbeddedValue 方法解析含占位符的字符串。
 *
 * 	5. 与 PropertySourcesPlaceholderConfigurer 的关系
 * 			PropertySourcesPlaceholderConfigurer 是 Spring 中用来加载外部属性源（如 .properties 文件）并将其注册到 Spring 环境中的一个工具。
 * 			它实际上实现了 PropertyPlaceholderConfigurer 接口，并且是 Spring Boot 默认配置的一部分。
 * 			EmbeddedValueResolver 利用了 PropertySourcesPlaceholderConfigurer 加载的属性来进行占位符解析。
 *
 * 	6. SpEL 表达式的支持
 * 		除了简单的属性占位符之外，EmbeddedValueResolver 还支持 SpEL 表达式。这使得你可以执行更为复杂的逻辑运算、访问对象属性、调用方法等。例如：
 * 		比如 BeanExpressionResolver 用于解析 SpEL 表达式
 *
 * <br>
 * {@link StringValueResolver} adapter for resolving placeholders and
 * expressions against a {@link ConfigurableBeanFactory}.
 * <br> StringValueResolver 的适配器，用于解析针对 ConfigurableBeanFactory 的占位符和表达式。
 *
 * <p>Note that this adapter resolves expressions as well, in contrast
 * to the {@link ConfigurableBeanFactory#resolveEmbeddedValue} method.
 * The {@link BeanExpressionContext} used is for the plain bean factory,
 * with no scope specified for any contextual objects to access.
 *<br> 注意，此适配器同时解析表达式，与 ConfigurableBeanFactory.resolveEmbeddedValue 方法不同。
 * 使用的 BeanExpressionContext 是针对普通 bean 工厂的，没有任何上下文对象可以访问。
 *
 *
 *
 * @author Juergen Hoeller
 * @since 4.3
 * @see ConfigurableBeanFactory#resolveEmbeddedValue(String)
 * @see ConfigurableBeanFactory#getBeanExpressionResolver()
 * @see BeanExpressionContext
 */
public class EmbeddedValueResolver implements StringValueResolver {

	private final BeanExpressionContext exprContext;

	@Nullable
	private final BeanExpressionResolver exprResolver;


	public EmbeddedValueResolver(ConfigurableBeanFactory beanFactory) {
		this.exprContext = new BeanExpressionContext(beanFactory, null);
		this.exprResolver = beanFactory.getBeanExpressionResolver();
	}


	@Override
	@Nullable
	public String resolveStringValue(String strVal) {
		String value = this.exprContext.getBeanFactory().resolveEmbeddedValue(strVal);
		if (this.exprResolver != null && value != null) {
			Object evaluated = this.exprResolver.evaluate(value, this.exprContext);
			value = (evaluated != null ? evaluated.toString() : null);
		}
		return value;
	}

}
